<?php

declare(strict_types=1);
/**
 * This file is part of MoChat.
 * @link     https://mo.chat
 * @document https://mochat.wiki
 * @contact  group@mo.chat
 * @license  https://github.com/mochat-cloud/mochat/blob/master/LICENSE
 */
namespace MoChat\App\WorkDepartment\Logic;

use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Logger\LoggerFactory;
use MoChat\App\Corp\Contract\CorpContract;
use MoChat\App\Corp\Utils\WeWorkFactory;
use MoChat\App\WorkDepartment\Contract\WorkDepartmentContract;
use MoChat\App\WorkEmployee\Queue\SyncEmployeeQueue;
use Psr\SimpleCache\CacheInterface;

/**
 * Class SyncDepartmentDetailLogic.
 */
class SyncDepartmentDetailLogic
{
    /**
     * 是否正在同步某企业员工.
     */
    public const LAST_SYNC_EMPLOYEE_TIME = 'sync_employee:%s';

    /**
     * @Inject
     * @var WorkDepartmentContract
     */
    protected $workDepartmentService;

    /**
     * @Inject
     * @var CorpContract
     */
    protected $corpService;

    /**
     * @var StdoutLoggerInterface
     */
    protected $logger;

    /**
     * @Inject
     * @var WeWorkFactory
     */
    protected $weWorkFactory;

    /**
     * @Inject
     * @var SyncEmployeeQueue
     */
    protected $syncEmployeeQueue;

    /**
     * @Inject
     * @var CacheInterface
     */
    protected $cache;

    public function handle(int $corpId, int $wxDepartmentId, int $lastDepartment)
    {
        $loggerFactory = make(LoggerFactory::class);
        $this->logger = $loggerFactory->get(__CLASS__);
        $this->logger->debug(sprintf('同步部门详情逻辑开始, 企业ID:%s, 部门ID:%s', (string) $corpId, (string) $wxDepartmentId));

        $this->getDepartmentDetail($corpId, $wxDepartmentId);

        $this->checkAndStartSyncEmployee($corpId, $lastDepartment);
    }

    private function getDepartmentDetail(int $corpId, int $wxDepartmentId): int
    {
        if (empty($wxDepartmentId)) {
            return 0;
        }

        // 强制使用应用Token获取部门详情，解决通讯录方式无法获取问题
        $weWorkApp = $this->weWorkFactory->getUserApp($corpId, true);
        $wxResult = $weWorkApp->department->get($wxDepartmentId);

        if ($wxResult['errcode'] !== 0) {
            $this->logger->error(sprintf('获取部门详情出错，企业ID:%s，部门ID:%s，错误信息：%s', (string) $corpId, (string) $wxDepartmentId, $wxResult['errmsg']));
            return 0;
        }

        if (empty($wxResult['department']['name'])) {
            $this->logger->error(sprintf('获取部门详情出错，企业ID:%s，部门ID:%s，错误信息：%s', (string) $corpId, (string) $wxDepartmentId, '代开发自建应用需要管理员授权才返回部门名称，请检查对应授权信息是否完整'));
            return 0;
        }

        return $this->createOrUpdateDepartment($corpId, $wxResult['department']);
    }

    /**
     * 创建或更新部门.
     */
    private function createOrUpdateDepartment(int $corpId, array $wxDepartment): int
    {
        if ($departmentId = $this->isExistsDepartment($corpId, $wxDepartment)) {
            return $this->updateDepartment($corpId, $wxDepartment, $departmentId);
        }
        return $this->createDepartment($corpId, $wxDepartment);
    }

    /**
     * 判断部门是否存在.
     */
    private function isExistsDepartment(int $corpId, array $wxDepartment): int
    {
        $department = $this->workDepartmentService->getWorkDepartmentByCorpIdWxDepartmentId($corpId, $wxDepartment['id']);
        return ! empty($department) ? $department['id'] : 0;
    }

    /**
     * 创建部门.
     */
    private function createDepartment(int $corpId, array $wxDepartment): int
    {
        $parentId = $this->getParentId($corpId, $wxDepartment['parentid']);

        $createData = [
            'corp_id' => $corpId,
            'parent_id' => $parentId,
            'wx_department_id' => $wxDepartment['id'],
            'name' => $wxDepartment['name'],
            'wx_parentid' => $wxDepartment['parentid'],
            'order' => $wxDepartment['order'],
            'created_at' => date('Y-m-d H:i:s'),
        ];
        $departmentId = $this->workDepartmentService->createWorkDepartment($createData);
        $this->updateDepartmentPathLevel($corpId, $departmentId, $parentId, $wxDepartment['parentid']);
        return $departmentId;
    }

    /**
     * 更新部门.
     */
    private function updateDepartment(int $corpId, array $wxDepartment, int $departmentId): int
    {
        $parentId = $this->getParentId($corpId, $wxDepartment['parentid']);

        $updateData = [
            'corp_id' => $corpId,
            'parent_id' => $parentId,
            'wx_department_id' => $wxDepartment['id'],
            'name' => $wxDepartment['name'],
            'wx_parentid' => $wxDepartment['parentid'],
            'order' => $wxDepartment['order'],
        ];
        $this->workDepartmentService->updateWorkDepartmentById($departmentId, $updateData);
        $this->updateDepartmentPathLevel($corpId, $departmentId, $parentId, $wxDepartment['parentid']);
        return $departmentId;
    }

    /**
     * 获取父id.
     */
    private function getParentId(int $corpId, int $wxParentId): int
    {
        $parentDepartment = $this->workDepartmentService->getWorkDepartmentByCorpIdWxDepartmentId($corpId, $wxParentId, ['id']);
        if (empty($parentDepartment)) {
            return $this->getDepartmentDetail($corpId, $wxParentId);
        }
        return $parentDepartment['id'];
    }

    /**
     * 更新部门路径.
     */
    private function updateDepartmentPathLevel(int $corpId, int $departmentId, int $parentId, int $wxParentId)
    {
        if ($wxParentId == 0) {
            $level = 1;
            $path = "#{$departmentId}#";
        } else {
            $parentDepartment = $this->workDepartmentService->getWorkDepartmentById($parentId, ['id', 'path', 'level']);
            $level = $parentDepartment['level'] + 1;
            $path = $parentDepartment['path'] . "#{$departmentId}#";
        }
        $this->workDepartmentService->updateWorkDepartmentById($departmentId, [
            'path' => $path,
            'level' => $level,
        ]);
    }

    /**
     * 检测部门同步进度并开始同步员工.
     */
    private function checkAndStartSyncEmployee(int $corpId, int $lastDepartment)
    {
        // 是否为最后一个员工
        if ($lastDepartment !== 1) {
            return;
        }

        if (! $this->allowSyncEmployee($corpId)) {
            return;
        }

        // 最后一个部门同步完成，10秒后开始同步员工，防止队列并发执行时最后一个不准确问题
        $this->syncEmployeeQueue->syncEmployeeByCorpId([
            'corpId' => $corpId,
        ], 10);

        $this->setSyncEmployeeTime($corpId);
    }

    /**
     * 30分钟允许同步一次
     *
     * @return bool
     */
    private function allowSyncEmployee(int $corpId)
    {
        $lastSyncEmployeeTime = $this->cache->get(sprintf(self::LAST_SYNC_EMPLOYEE_TIME, (string) $corpId));
        if (empty($lastSyncEmployeeTime)) {
            return true;
        }

        if (time() - $lastSyncEmployeeTime > 1800) {
            return true;
        }

        return false;
    }

    private function setSyncEmployeeTime(int $corpId)
    {
        $this->cache->set(sprintf(self::LAST_SYNC_EMPLOYEE_TIME, (string) $corpId), time(), 60 * 30);
    }
}
